package com.hsu.apibackend.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hsu.apibackend.common.BaseResponse;
import com.hsu.apibackend.common.ErrorCode;
import com.hsu.apibackend.common.ResultUtils;
import com.hsu.apibackend.exception.BusinessException;
import com.hsu.apibackend.model.entity.DailyCheckIn;
import com.hsu.apibackend.model.vo.UserVO;
import com.hsu.apibackend.service.DailyCheckInService;
import com.hsu.apibackend.service.UserService;
import com.hsu.apibackend.utils.RedissonLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.Date;

/**
 * @Author: Hsu
 * @Date: 2024/09/10
 * @Version: v1.0.0
 * @Description 签到接口
 */
@RestController
@RequestMapping("/dailyCheckIn")
@Slf4j
public class DailyCheckInController {

    @Resource
    private DailyCheckInService dailyCheckInService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonLockUtil redissonLockUtil;

    // region 签到功能

    /**
     * 处理每日签到请求
     *
     * @param request 请求对象，包含当前用户信息
     * @return {@link BaseResponse}<{@link Boolean}> 返回签到操作是否成功
     */
    @PostMapping("/doCheckIn")
    @Transactional(rollbackFor = Exception.class)
    public BaseResponse<Boolean> doDailyCheckIn(HttpServletRequest request) {
        // 获取当前登录的用户
        UserVO loginUser = userService.getLoginUser(request);

        // 使用 Redisson 锁来确保并发情况下的签到唯一性
        String redissonLock = ("doDailyCheckIn_" + loginUser.getUserAccount()).intern();

        // 执行签到操作并使用分布式锁来保证事务的一致性
        return redissonLockUtil.redissonDistributedLocks(redissonLock, () -> {
            // 获取当天日期的开始时间和结束时间
            Date todayStart = getTodayStart();
            Date todayEnd = getTodayEnd();

            // 查询用户今天是否已经签到
            LambdaQueryWrapper<DailyCheckIn> dailyCheckInLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dailyCheckInLambdaQueryWrapper
                    .eq(DailyCheckIn::getUserId, loginUser.getId())
                    .between(DailyCheckIn::getCreateTime, todayStart, todayEnd);

            DailyCheckIn dailyCheckIn = dailyCheckInService.getOne(dailyCheckInLambdaQueryWrapper);

            // 如果用户今天已经签到，抛出异常
            if (ObjectUtils.isNotEmpty(dailyCheckIn)) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "签到失败,今日已签到");
            }

            // 创建新的签到记录
            dailyCheckIn = new DailyCheckIn();
            dailyCheckIn.setUserId(loginUser.getId());
            dailyCheckIn.setAddPoints(10);  // 默认增加 10 积分
            dailyCheckIn.setCreateTime(new Date());  // 设置签到时间为当前时间

            // 保存签到记录并更新用户钱包余额
            boolean dailyCheckInResult = dailyCheckInService.save(dailyCheckIn);
            boolean addWalletBalance = userService.addWalletBalance(loginUser.getId(), dailyCheckIn.getAddPoints());

            // 如果签到保存和钱包更新失败，抛出异常
            boolean result = dailyCheckInResult && addWalletBalance;
            if (!result) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR);
            }

            // 返回成功响应
            return ResultUtils.success(true);
        }, "签到失败");
    }

    // 获取当天开始时间的工具方法
    private Date getTodayStart() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }

    // 获取当天结束时间的工具方法
    private Date getTodayEnd() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        return calendar.getTime();
    }

    // endregion
}
